﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Input;
using System.Windows.Data;
using System.Windows.Art;

namespace System.Windows.Elements
{
    public class PaletteChrome : FrameworkElement
    {
        public static readonly DependencyProperty MixerProperty, ReductionFactorProperty, IsAlphaDisplayedProperty;
        public static readonly DependencyProperty BackgroundProperty;

        private WriteableBitmap colorMixerPalette;
        private Byte[] palettePixels;
        private Int32 paletteWidth, paletteHeight, paletteStride, paletteBpp;
        private Double dpiX, dpiY;

        static PaletteChrome()
        {
            FrameworkPropertyMetadata metadata;
            metadata = new FrameworkPropertyMetadata();
            metadata.AffectsRender = true;
            metadata.PropertyChangedCallback = (d, e) => ((PaletteChrome)d).InvalidatePalette();
            PaletteChrome.MixerProperty = DependencyProperty.Register("Mixer", typeof(Func<Point, IColor>), typeof(PaletteChrome), metadata);

            metadata = new FrameworkPropertyMetadata();
            metadata.DefaultValue = 8.0;
            metadata.AffectsRender = true;
            metadata.PropertyChangedCallback = (d, e) => ((PaletteChrome)d).InvalidatePalette();
            metadata.CoerceValueCallback = (d, o) => ((Double)o < 1) ? 1.0 : o;
            PaletteChrome.ReductionFactorProperty = DependencyProperty.Register("ReductionFactor", typeof(Double), typeof(PaletteChrome), metadata);

            PaletteChrome.IsAlphaDisplayedProperty = DependencyProperty.Register("IsAlphaDisplayed", typeof(Boolean), typeof(PaletteChrome), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
            PaletteChrome.BackgroundProperty = DependencyProperty.Register("Background", typeof(Brush), typeof(PaletteChrome), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
        }

        public PaletteChrome()
        {
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);
            if (this.Background != null) drawingContext.DrawRectangle(this.Background, null, new Rect(this.RenderSize));
            drawingContext.DrawImage(this.colorMixerPalette, new Rect(0.0, 0.0, this.RenderSize.Width, this.RenderSize.Height));
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);
            var newSize = sizeInfo.NewSize;
            if (newSize.Width != 0 && newSize.Height != 0) this.InitializePalette(newSize.Width, newSize.Height);
        }


        private void InitializePalette(Double width, Double height)
        {
            Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
            this.dpiX = 96D * m.M11;
            this.dpiY = 96D * m.M22;

            this.paletteBpp = 4; // 24-bit color
            this.paletteHeight = (Int32)Math.Round(height / this.ReductionFactor);
            this.paletteWidth = (Int32)Math.Round(width / this.ReductionFactor);
            this.paletteStride = this.paletteWidth * this.paletteBpp;

            this.palettePixels = new Byte[this.paletteHeight * this.paletteStride];

            this.colorMixerPalette = new WriteableBitmap(this.paletteWidth, this.paletteHeight, this.dpiX, this.dpiY, PixelFormats.Bgra32, null);
            this.InvalidateProperty(PaletteChrome.MixerProperty);
        }

        public void InvalidatePalette()
        {
            var mixer = this.Mixer;
            //if (mixer == null) this.colorMixerPalette.WritePixels(new Int32Rect(0, 0, this.paletteWidth, this.paletteHeight), new Byte[this.palettePixels.Length], this.paletteStride, 0);
            if (this.colorMixerPalette != null && mixer != null)
            {
                Boolean isAlphaDisplayed = this.IsAlphaDisplayed;
                for (Int32 i = 0; i < this.paletteHeight; i++)
                {
                    for (Int32 j = 0; j < this.paletteWidth; j++)
                    {
                        var offset = this.paletteBpp * (this.paletteWidth * i + j);
                        var z = new Point((Double)j / this.paletteWidth, (Double)i / this.paletteHeight);
                        var tempColor = mixer(z).ToColor();
                        this.palettePixels[offset] = tempColor.B;
                        this.palettePixels[offset + 1] = tempColor.G;
                        this.palettePixels[offset + 2] = tempColor.R;
                        this.palettePixels[offset + 3] = (isAlphaDisplayed) ? tempColor.A : (Byte)255;
                    }
                }

                this.colorMixerPalette.WritePixels(new Int32Rect(0, 0, this.paletteWidth, this.paletteHeight), this.palettePixels, this.paletteStride, 0);
            }
        }

        public Func<Point, IColor> Mixer
        {
            get { return (Func<Point, IColor>)this.GetValue(PaletteChrome.MixerProperty); }
            set { this.SetValue(PaletteChrome.MixerProperty, value); }
        }

        public Double ReductionFactor
        {
            get { return (Double)this.GetValue(PaletteChrome.ReductionFactorProperty); }
            set { this.SetValue(PaletteChrome.ReductionFactorProperty, value); }
        }

        public Boolean IsAlphaDisplayed
        {
            get { return (Boolean)this.GetValue(PaletteChrome.IsAlphaDisplayedProperty); }
            set { this.SetValue(PaletteChrome.IsAlphaDisplayedProperty, value); }
        }

        public Brush Background
        {
            get { return (Brush)this.GetValue(PaletteChrome.BackgroundProperty); }
            set { this.SetValue(PaletteChrome.BackgroundProperty, value); }
        }
    }
}
